package owg.util.opengl;

import java.awt.Color;
import java.util.Arrays;

import owg.util.Calc;
/**Provides a well-organized way to handle OpenGL float colors.
 * The basic implementation is immutable, so its instances may be recklessly shared.*/
public class ColorF {
	public static final ColorF 
	BOGUSBLUE = new ColorF(.25f, .53f, .64f, 1f),
	BLACK = 	new ColorF(new float[]{0,0,0,1}),
	WHITE = 	new ColorF(new float[]{1,1,1,1}),
	DKGRAY = 	new ColorF(new float[]{0.25f,0.25f,0.25f,1}),
	GRAY = 		new ColorF(new float[]{0.5f,0.5f,0.5f,1}),
	LTGRAY = 	new ColorF(new float[]{0.75f,0.75f,0.75f,1}),
	RED = 		new ColorF(new float[]{1,0,0,1}),
	GREEN = 	new ColorF(new float[]{0,1,0,1}),
	BLUE = 		new ColorF(new float[]{0,0,1,1}),
	CYAN = 		new ColorF(new float[]{0,1,1,1}),
	MAGENTA = 	new ColorF(new float[]{1,0,1,1}),
	YELLOW = 	new ColorF(new float[]{1,1,0,1}),
	ORANGE = 	new ColorF(new float[]{1,0.5f,0,1}),
	RUST = 		new ColorF(new float[]{0x8b/255f,0x23/255f,0x0a/255f,1});
	
	/**Parses the integer as RGB bytes. 
	 * The three (unsigned) least significant bytes of the integer are normalized and returned in a new float array.
	 * The resulting array will have 4 elements. The alpha component defaults to 1.*/
	public static float[] rgbToFloat(int hexColor)
	{
		float[] color = new float[4];
		color[3] = 1.0f;
		rgbToFloat(color, hexColor);
		return color;
	}
	/**Parses the integer as RGB bytes. 
	 * The three (unsigned) least significant bytes of the integer are normalized and inserted into the color array.*/
	public static void rgbToFloat(float[] color, int hexColor)
	{
		color[0] = (float)((hexColor&0xFF0000)>>16)/0xFF;
		color[1] = (float)((hexColor&0x00FF00)>> 8)/0xFF;
		color[2] = (float)((hexColor&0x0000FF)    )/0xFF;
	}
	
	/**Parses the integer as ARGB bytes, where A is the most significant byte and B is the least significant byte. 
	 * The four bytes of the integer are normalized and returned in a new float array.
	 * The resulting array will have 4 elements.*/
	public static float[] argbToFloat(int hexColor)
	{
		float[] color = new float[4];
		argbToFloat(color, hexColor);
		return color;
	}
	/**Parses the integer as ARGB bytes, where A is the most significant byte and B is the least significant byte.
	 * The four bytes of the integer are normalized and returned in the color array.*/
	public static void argbToFloat(float[] color, int hexColor)
	{
		color[0] = (float)((hexColor&0x00FF0000)>>16)/0xFF;
		color[1] = (float)((hexColor&0x0000FF00)>> 8)/0xFF;
		color[2] = (float)((hexColor&0x000000FF)    )/0xFF;
		color[3] = (float)((hexColor&0xFF000000)>>>24)/0xFF;
	}
	/**Encodes the 4 element float color into an integer as ARGB, 
	 * where A is the most significant byte and B is the least significant byte.*/
	public static int floatToARGB(float[] color) {
		return	(((int)(color[3]*255)&0xFF)<<24) |
			(((int)(color[0]*255)&0xFF)<<16) |
			(((int)(color[1]*255)&0xFF)<< 8) |
			(((int)(color[2]*255)&0xFF));
	}
	/**Represents a mutable ColorF. <br/>
	 * This is useful for objects that have a color attribute that needs to change over time.*/
	public static class ColorFMutable extends ColorF {
		/**A public view of the 4-element backing array for the color.*/
		public final float[] c = data;
		public ColorFMutable(String str){
			super(str);
		}
		public ColorFMutable(ColorF color) {
			super(color);
		}
		
		public ColorFMutable(Color color){
			super(color);
		}
		
		public ColorFMutable(ColorF color, float alpha) {
			super(color, alpha);
		}
		
		public ColorFMutable(float[] color) {
			super(color);
		}
		public ColorFMutable(float[] color, float alpha) {
			super(color, alpha);
		}
		public ColorFMutable(float r, float g, float b, float a) {
			super(r,g,b,a);
		}
		/**
		 * @param color The 32 bit integer color, formatted as ARGB,
		 * where A is the most significant byte and B is the least significant byte.
		 * @param useAlpha Whether to use the Alpha byte from the integer. If false, the alpha is set to 1.
		 */
		public ColorFMutable(int color, boolean useAlpha) {
			super(color, useAlpha);
		}
		public ColorFMutable(byte r, byte g, byte b, byte a) {
			super(r,g,b,a);
		}

		/**
		 * Set the color's red component, with values 0x00 to 0xFF.
		 * @return This color
		 */
		public ColorF setRed(byte r) {
			data[0] = (r&0xFF)/255f;
			return this;
		}
		/**
		 * Set the color's green component, with values 0x00 to 0xFF.
		 * @return This color
		 */
		public ColorF setGreen(byte g) {
			data[1] = (g&0xFF)/255f;
			return this;
		}
		/**
		 * Set the color's blue component, with values 0x00 to 0xFF.
		 * @return This color
		 */
		public ColorF setBlue(byte b) {
			data[2] = (b&0xFF)/255f;
			return this;
		}
		/**
		 * Set the color's alpha, with values 0x00 to 0xFF.
		 * @return This color
		 */
		public ColorF setAlpha(byte a) {
			data[3] = (a&0xFF)/255f;
			return this;
		}

		/**
		 * Set the color's red component, with values 0.0f to 1.0f.
		 * @return This color
		 */
		public ColorF setRed(float r) {
			data[0] = r;
			return this;
		}
		/**
		 * Set the color's green component, with values 0.0f to 1.0f.
		 * @return This color
		 */
		public ColorF setGreen(float g) {
			data[1] = g;
			return this;
		}
		/**
		 * Set the color's blue component, with values 0.0f to 1.0f.
		 * @return This color
		 */
		public ColorF setBlue(float b) {
			data[2] = b;
			return this;
		}
		/**
		 * Set the color's alpha, with values 0.0f to 1.0f.
		 * @return This color
		 */
		public ColorF setAlpha(float a) {
			data[3] = a;
			return this;
		}

		/**
		 * Set the color to the indicated components, with values 0.0f to 1.0f.
		 * @return This color
		 */
		public ColorF set(float r, float g, float b, float a) {
			Calc.fill(data, r, g, b, a);
			return this;
		}
		/**
		 * Set the color to the indicated components, with values 0x00 to 0xFF.
		 * @return This color
		 */
		public ColorF set(byte r, byte g, byte b, byte a) {
			Calc.fill(data, (r&0xFF)/255f, (g&0xFF)/255f, (b&0xFF)/255f, (a&0xFF)/255f);
			return this;
		}

		/**
		 * Set the color to the indicated components, in rgba order.
		 * @return This color
		 */
		public ColorF set(float[] src) {
			System.arraycopy(src, 0, data, 0, 4);
			return this;
		}

		/**
		 * Set the color's ARGB components to the indicated int color.
		 * @return This color
		 * @see #argbToFloat(float[], int)
		 */
		public ColorF setARGB(int argb) {
			argbToFloat(data, argb);
			return this;
		}
		/**
		 * Set the color's RGB components to the indicated int color.
		 * @return This color
		 * @see #rgbToFloat(float[], int)
		 */
		public ColorF setRGB(int rgb) {
			rgbToFloat(data, rgb);
			return this;
		}

		/**
		 * Set the color to the indicated color.
		 * @return This color
		 */
		public ColorF set(ColorF srcColor) {
			data[0] = srcColor.data[0];
			data[1] = srcColor.data[1];
			data[2] = srcColor.data[2];
			data[3] = srcColor.data[3];
			return this;
		}
		/**
		 * Invert the red, green and blue components of the color.
		 * @return This color
		 */
		public ColorF invertRGB()
		{
			data[0] = 1-data[0];
			data[1] = 1-data[1];
			data[2] = 1-data[2];
			return this;
		}
	}
	
	/**The backing array for the color. Is not exposed to the application in immutable ColorF objects.*/
	protected final float[] data;
	/**
	 * Create a color from the string. The string may be a hex color, e.g. 0xFFFFFF or #FFFFFF, or a comma-separated rgba float tuple, 
	 * formatted as 1,1,1,1 or r:1, g:1, b:1, a:1. The component names ("r:") are ignored. If the number of elements is less than 4, 
	 * then only the first components(starting with r) are filled in, alpha defaults to 1, and other components default to 0.
	 */
	public ColorF(String str) throws IllegalArgumentException
	{
		try
		{
			final int hexOffset;
			if(str.startsWith("#"))
				hexOffset = 1;
			else if(str.startsWith("0x"))
				hexOffset = 2;
			else
				hexOffset = -1;
			if(hexOffset >= 0)
			{
				String ts = str.substring(hexOffset).trim();
				int hexColor = Integer.parseUnsignedInt(ts, 16);
				if(ts.length() > 6)
					data = argbToFloat(hexColor);
				else
					data = rgbToFloat(hexColor);
			}
			else
			{
				data = new float[]{0,0,0,1};
				String[] in = str.split(",");
				try
				{
					for(int i = 0; i<Math.min(4, in.length); i++)
					{
						int pos = in[i].lastIndexOf(':');
						if(pos == -1)
							data[i] = Float.parseFloat(in[i]);
						else
							data[i] = Float.parseFloat(in[i].substring(pos+1));
					}
				}
				catch (NumberFormatException e)
				{
					throw new IllegalArgumentException("Error in parsing "+str, e);
				}
			}
		}
		catch(Exception e)
		{
			throw new IllegalArgumentException("Could not parse \""+str+"\" into a "+getClass().getSimpleName(), e);
		}
		
	}
	
	public ColorF(ColorF color) {
		data = color.data.clone();
	}
	public ColorF(ColorF color, float alpha) {
		this(color.data, alpha);
	}
	
	public ColorF(Color color){
		this(color.getRGB(), true);
	}
	
	public ColorF(float[] color, float alpha){
		data = new float[4];
		data[3] = alpha;
		System.arraycopy(color, 0, data, 0, Math.min(3, color.length));
	}
	
	public ColorF(float[] color) {
		if(color.length == 4)
			data = color.clone();
		else
		{
			data = new float[4];
			data[3] = 1;
			System.arraycopy(color, 0, data, 0, color.length);
		}
	}
	public ColorF(float r, float g, float b, float a) {
		data = new float[]{r,g,b,a};
	}
	/**
	 * @param color The 32 bit integer color, formatted as ARGB,
	 * where A is the most significant byte and B is the least significant byte.
	 * @param useAlpha Whether to use the Alpha byte from the integer. If false, the alpha is set to 1.
	 */
	public ColorF(int color, boolean useAlpha) {
		if(useAlpha)
			data = argbToFloat(color);
		else
			data = rgbToFloat(color);
	}
	public ColorF(byte r, byte g, byte b, byte a) {
		data = new float[]{(r&0xFF)/255f,(g&0xFF)/255f,(b&0xFF)/255f,(a&0xFF)/255f};
	}
	
	public float getRed() {
		return data[0];
	}
	public float getGreen() {
		return data[1];
	}
	public float getBlue() {
		return data[2];
	}
	public float getAlpha() {
		return data[3];
	}
	public int getARGB() {
		return floatToARGB(data);
	}
	/**@return A new 4 element float array representing the color.*/
	public float[] getFloat() {
		return data.clone();
	}
	/**
	 * @param index The index of the desired color component; Red = 0, Green = 1, Blue = 2, Alpha = 3.
	 * @return The color component with the indicated index.
	 */
	public float get(int index) {
		return data[index];
	}
	
	public ColorFMutable getMutableCopy() {
		return new ColorFMutable(data);
	}
	@Override
	public String toString() {
		return "r: "+data[0]+" g: "+data[1]+" b: "+data[2]+" a: "+data[3];
	}
	/**Whether the color is binary equal to the other color. False if the other is not a ColorF.*/
	@Override
	public boolean equals(Object obj)
	{
		return obj != null && obj instanceof ColorF && Arrays.equals(((ColorF)obj).data, data);
	}
}
